Release 5.5.2: aggregation pipeline encoding and with_session scoping fixes#19
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the Ruby Parse Server SDK to treat fiber-local ambient sessions (Parse.with_session) as an authorization scope for aggregation-style query terminals, ensuring scoped aggregations auto-route to mongo-direct (or fail closed) instead of silently returning unscoped/master-key results. It also bumps the gem version to 5.5.2 and documents the behavior in the changelog.
Changes:
- Treat
Parse.with_session’s ambient session token as scope in aggregation scoping checks (distinct_query_is_scoped?,query_is_scoped?), so aggregations promote to mongo-direct or raiseMongoDirectRequiredwhen direct Mongo is unavailable. - Force REST
/aggregatecalls to useuse_master_key: trueunless the caller explicitly setuse_master_key: false, preventing ambient sessions from suppressing the master key. - Add tests for ambient-session aggregation behavior and for respecting explicit
use_master_key: false, plus changelog/version updates.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
lib/parse/query.rb |
Implements ambient-session scoping for aggregations, fail-closed behavior when mongo-direct is unavailable, REST aggregate master-key forcing, and adds a REST failure warning. |
test/lib/parse/aggregation_auto_promotion_test.rb |
Adds regression/behavior tests for ambient Parse.with_session scoping and explicit use_master_key: false on REST aggregation paths. |
lib/parse/stack/version.rb |
Bumps version to 5.5.2. |
CHANGELOG.md |
Documents the 5.5.2 changes around scoped aggregations in Parse.with_session and the REST aggregate master-key behavior. |
Comments suppressed due to low confidence (3)
lib/parse/query.rb:6565
- This raises
MongoDirectRequiredfor scoped aggregations, but the exception message (raised byraise_scoped_aggregation_requires_mongo_direct!) only mentionssession_token / scope_to_user / scope_to_role. Since ambientParse.with_sessionis now considered scope, the error text should mention the ambient session too so callers understand why they’re being blocked.
# Format the group field name
formatted_group_field = @query.send(:format_aggregation_field, @group_field)
# Auto-promote scoped queries to mongo-direct so the SDK's three-layer
lib/parse/query.rb:7259
- Avoid referencing approximate line numbers in comments ("line ~3575") since they drift over time and become misleading. Prefer pointing to the method / behavior being mirrored instead.
private
# Execute a date-based group aggregation operation.
lib/parse/query.rb:6560
- The scoped-query explanation just above this block is now incomplete:
query_is_scoped?includes the ambient session fromParse.with_session, but the comment still lists onlysession_token / acl_user / acl_role. Update it so future maintainers don’t miss thatwith_sessionalso triggers mongo-direct promotion / fail-closed behavior.
# alternatives are "$size on a non-array" (server error) and
# lexicographic array compare (silently wrong), neither of which is
# what the caller meant.
validate_sort_target_for_operation!(operation)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Copilot stopped reviewing on behalf of
AdrianCurtin due to an error
June 11, 2026 14:41
Comment on lines
+303
to
+308
| env[:request_headers][HTTP_METHOD_OVERRIDE] = "GET" | ||
| env[:request_headers][CONTENT_TYPE] = "application/x-www-form-urlencoded" | ||
| # parse-sever looks for method overrides in the body under the `_method` param. | ||
| # so we will add it to the query string, which will now go into the body. | ||
| env[:body] = "_method=GET&" + env[:url].query | ||
| env[:url].query = nil |
Comment on lines
+6008
to
+6016
| rest_opts = @query.send(:_opts) | ||
| rest_opts[:use_master_key] = true unless rest_opts[:use_master_key] == false | ||
| @cached_response = @query.client.aggregate_pipeline( | ||
| @query.instance_variable_get(:@table), | ||
| @pipeline, | ||
| headers: {}, | ||
| raw_values: @raw_values, | ||
| raw_field_names: @raw_field_names, | ||
| **@query.send(:_opts), | ||
| **rest_opts, |
Comment on lines
+7365
to
+7368
| unless response.success? | ||
| warn "[Parse::GroupByDate] aggregate failed (#{@query.instance_variable_get(:@table)}" \ | ||
| " :#{@date_field} :#{@interval}): #{response.error.inspect}" | ||
| end |
Comment on lines
+265
to
+273
| Parse::MongoDB.define_singleton_method(:aggregate) do |_class_name, _pipeline, **_kw| | ||
| direct_called = true | ||
| [] | ||
| end | ||
| Parse.with_session("r:ambient-tok") { @query.count } | ||
| assert direct_called, "expected scoped #count (ambient session) to route through mongo-direct" | ||
| ensure | ||
| Parse::MongoDB.singleton_class.remove_method(:aggregate) if Parse::MongoDB.singleton_class.method_defined?(:aggregate) | ||
| end |
| else | ||
| env[:request_headers][HTTP_METHOD_OVERRIDE] = "GET" | ||
| env[:request_headers][CONTENT_TYPE] = "application/x-www-form-urlencoded" | ||
| # parse-sever looks for method overrides in the body under the `_method` param. |
Comment on lines
+362
to
+364
| def aggregate_request?(url) | ||
| url.path.to_s.include?("/aggregate/") | ||
| end |
1c05ef5 to
c12ebb3
Compare
Comment on lines
+6413
to
+6419
| if query_is_scoped? | ||
| if parse_mongodb_available? | ||
| return execute_group_aggregation_direct(operation, aggregation_expr, formatted_group_field) | ||
| else | ||
| @query.send(:raise_scoped_aggregation_requires_mongo_direct!) | ||
| end | ||
| end |
Comment on lines
+1824
to
+1825
| ambient = ambient_session_token | ||
| return true if ambient.is_a?(String) && !ambient.empty? |
Comment on lines
+6829
to
+6830
| ambient = @query.send(:ambient_session_token) | ||
| return true if ambient.is_a?(String) && !ambient.empty? |
Comment on lines
+7572
to
+7573
| ambient = @query.send(:ambient_session_token) | ||
| return true if ambient.is_a?(String) && !ambient.empty? |
Comment on lines
+1955
to
+1956
| encoded_len = { pipeline: long_pipeline.to_json }.to_a | ||
| .map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&").length |
Send JSON bodies for long-URL aggregate overrides and scope aggregations to ambient sessions. BodyBuilder now detects /aggregate/ requests and builds a JSON POST body (preserving pipeline as an Array and boolean params) to avoid Parse Server rejecting urlencoded pipeline strings. Query/aggregation logic was updated to consult the ambient Parse.with_session session token (in distinct_query_is_scoped? and query_is_scoped?), to auto-promote scoped aggregations to mongo-direct when available and to raise MongoDirectRequired when mongo-direct is unavailable. REST /aggregate calls force use_master_key: true unless explicitly set false to avoid ambient sessions suppressing the master key and causing 401/403s. Tests for ambient session scoping and the long-URL aggregate override were added/updated, CHANGELOG updated, and the stack version bumped to 5.5.2.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release 5.5.2. Two aggregation fixes plus scoping/security hardening for aggregations run inside
Parse.with_sessionblocks.Large aggregation pipelines no longer fail with "Invalid aggregate stage '0'"
group_by,group_by_date,distinct, or customaggregatepipeline with a large$in/$match) is rewritten from a GET to a POST carrying_method=GET, moving the query into the request body. The pipeline was sent in the body as a URL-encoded string, but Parse Server's aggregate endpoint only JSON-decodes query-string params, not body params — so the pipeline arrived as a raw string and was rejected withInvalid aggregate stage '0', causing the aggregation to return an empty result. The long-URL override now sends a JSON body for the aggregate endpoint so the pipeline is delivered as a real array (boolean params such asrawValuesare preserved as booleans). The historical URL-encoded override is unchanged forfindand other endpoints, which Parse Server already decodes correctly.Aggregations inside
Parse.with_sessionblocks are now scopedgroup_by_date,group_by,distinct, andcount(aggregation branch) now detect the ambient session token set byParse.with_sessionand treat the query as scoped — consistent with howParse::Client#requestalready scopes REST find/get/count calls in the same block. Previouslyquery_is_scoped?/distinct_query_is_scoped?consulted only the query instance's ownsession_token=/scope_to_user/scope_to_roleand ignoredParse.current_session_token, so an aggregation inside awith_sessionblock ran unscoped as the master key and returned all rows regardless of ACL. The checks now include the ambient: when scoped and mongo-direct is available the aggregation auto-promotes (ACL/CLP enforced); when scoped and mongo-direct is unavailable it fails closed withMongoDirectRequiredrather than silently leaking rows.group_by_datenow also fails closed (MongoDirectRequired) when the query is scoped but mongo-direct is unavailable — matching the existing behavior ofgroup_by,distinct, andcount. Previouslygroup_by_datesilently fell back to the REST/aggregateendpoint in that case.group_by_date,group_by, and pipeline-based aggregations called inside aParse.with_sessionblock returned empty results{}. The ambient session token was forwarded as an HTTP session-token header (suppressing the master key), causing Parse Server's REST/aggregateendpoint — which is master-key-only — to return a 401/403. The REST aggregate call sites now forceuse_master_key: trueso the ambient cannot suppress it, unless the caller explicitly setuse_master_key: false.Tests
body_builder_method_override_test.rbcovering the long-URL GET→POST override: JSON body for the aggregate endpoint (pipeline preserved as an array, booleans preserved), unchanged URL-encoded body forfind, short URLs left as GET, and the/aggregate/path guard.aggregation_auto_promotion_test.rbwith ambient-session promote / fail-closed coverage forgroup_by,group_by_date,distinct,count,Query#aggregate, andGroupBy#raw, plus explicituse_master_key: falsehandling.